package com.liu;

/**
 * @Author: 刘浩然
 * @Date: 2021/6/21 13:12
 */

;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

/**
 * 相当于Springl中ClassPathXmlApplicationContext
 * 1、解析xml文件
 * 2、获取xml文件所有的节点内容
 * 3、利用java反射机制创建实例对象
 * 4、将实例对象添加到ioc容器
 * czy say ：SO EASY
 */
public class XmlApplicationContext {
    // xml路径
    private String xmlPath;
    // 存放bean（也就是ioc的容器）
    private static HashMap map = new HashMap();
    //存放从xml中解析出来的所有bean
    private List<BeanConfig> beanConfigs = new ArrayList<BeanConfig>();

    //对外的构造函数
    public XmlApplicationContext(String xmlPath) {
        this.xmlPath = xmlPath;
        try {
            //解析xml到beanConfigs
            getAllElements();
            //初始化ioc容器
            init();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 读取xml文件所有的节点内容，并放入beanConfigs集合
     */
    private void getAllElements() throws Exception {
        //1.指定路径加载xml到jvm，形成输入流，xml中的所有内容都在这个流中
        InputStream is = XmlApplicationContext.class.getClassLoader().getResourceAsStream(xmlPath);
        //2.创建解析对象
        SAXReader reader = new SAXReader();
        //3.获得文档对象（整个xml文件->将输入流转换成文档对象）
        Document document = reader.read(is);
        //4.获取根节点，也就是对应spring-ioc.xml中的树干beans
        Element rootElement = document.getRootElement();
        //5.获取元素迭代器,用于获取所有节点内容
        Iterator iterator = rootElement.elementIterator();
        while (iterator.hasNext()) {
            //用于接收xml中bean的属性值
            BeanConfig beanConfig = new BeanConfig();
            //获取beans的子节点bean
            Element bean = (Element) iterator.next();
            // 获取bean的属性迭代器
            Iterator beanAttributes = bean.attributeIterator();
            while (beanAttributes.hasNext()) {
                Attribute attribute = (Attribute) beanAttributes.next();
                String attributeName = attribute.getName();//属性名
                String attributeData = (String) attribute.getData();//属性值
                if ("id".equals(attributeName)) {
                    beanConfig.setId(attributeData);
                } else if ("class".equals(attributeName)) {
                    beanConfig.setClazz(attributeData);
                } else if ("factory-method".equals(attributeName)) {
                    beanConfig.setFactoryMethod(attributeData);
                } else {
                    beanConfig.setFactoryBean(attributeData);
                }
            }
            //将这条bean的属性值放入beanConfigs集合
            beanConfigs.add(beanConfig);
        }
    }

    /**
     * 这里初始化ioc容器是关键
     * 这里就要利用java的反射机制来创建bean实例
     *
     * @throws Exception
     */
    private void init() throws Exception {
        /**
         * 这里说一点：
         * 看spring-ioc.xml中：bean标签有class属性时就没有factory-bean属性，反之
         * 有class属性时，就要判断是空参构造创建还是静态工厂创建，也就是判断有没有factory-method属性
         * 有factory-bean时，必然是实例工厂创建-必须有factory-method
         * 这点要捋清楚
         */
        for (BeanConfig bean : beanConfigs) {
            if (null != bean.getClazz()) {
                //通过全限定类名拿到Class实例clazz
                Class clazz = Class.forName(bean.getClazz());
                if (null != bean.getFactoryMethod()) {//静态工厂创建bean实例
                    //方法名获取method对象
                    Method method = clazz.getDeclaredMethod(bean.getFactoryMethod());
                    putBean(bean.getId(), method.invoke(null));
                } else {//构造器创建bean实例
                    putBean(bean.getId(), clazz.newInstance());
                }
            } else if (null != bean.getFactoryBean()) {//实例工厂创建bean实例
                //从容器中拿到实体bean
                Object obj = getBean(bean.getFactoryBean());
                Method method = obj.getClass().getDeclaredMethod(bean.getFactoryMethod());
                putBean(bean.getId(), method.invoke(obj));
            } else {
                System.out.println("czy不知道在搞什么鬼");
            }
        }
    }

    /**
     * 添加实例对象到ioc容器
     *
     * @param id：就是bean中自定义的id
     * @param object ：就是反射机制创建的实例对象
     */
    public void putBean(String id, Object object) {
        map.put(id, object);
    }

    /**
     * 通过id获取实例对象
     *
     * @param id：就是bean中自定义的id
     * @return
     */
    public Object getBean(String id) {
        return map.get(id);
    }
}
